import {List, Cask} from 'cascadium';
import Accrual from '../../Accrual';

function assignDest(morn, dusk){
  morn.dest = dusk.curr;
  dusk.dest = morn.curr;
}

const toFixed2 = (number) => parseFloat(number.toFixed(2));

// mark can be either 'morn' or 'dusk' only;
const accumulate = (list, col, mark) => list
  .map(({accrual: {[col]: val}}) => val)
  .reduce(([last, ...rest], next) => [toFixed2(next + last), last, ...rest], [0])
  .reverse()
  .map((accrual, index) => ({accrual, [mark]: index}));

function alternate({list: morns, col: mornCol}, {list: dusks, col: duskCol}) {

  const paired = new Cask();
  for (let m = 0; m < morns.length; m++)
  for (let d = 0; d < dusks.length; d++)
  if (morns[m].accrual[mornCol] === dusks[d].accrual[duskCol]) {
    const [morn] = morns.splice(m, 1);
    const [dusk] = dusks.splice(d, 1);

    (m > 0) && m--;
    (d > 0) && d--;

    paired.push([morn, dusk]);
  }

  const mornVals = accumulate(morns, mornCol, 'morn');
  const duskVals = accumulate(dusks, duskCol, 'dusk');

  // 上面两步所得到的结果，分别是借贷方发生额从0累加之后的边界值。如果把每个发生
  // 额想象成某个长度，那么n个长度会产生n+1个边界。参考篱笆和桩的问题。

  const sortedIndex = [...mornVals, ...duskVals]

    // 请牢记篱笆和桩的数量关系，我们将两个数组组合之后排序，显然morns和dusks原先
    // 的数值会穿插在一起，这样我们就能看到两个数组中，morns的某个桩，会落入dusks
    // 的哪两个桩之间，vice versa for dusks。
    .sort(({accrual: A}, {accrual: B}) => A - B)

    // 这一步操作即计算桩的穿插关系。除首尾重复的桩之外，第i个morn桩之后出现的所有
    // dusk桩都属于第i个morn桩，因此当一个dusk桩出现时，它所属的morn桩总是上一个
    // 桩所属的morn桩, vice versa for morns。在完成了桩的分配之后，我们去掉首尾
    // 两个多出来的桩。

    .reduce(([last, ...rest], {accrual, morn, dusk}) => {
        return last === undefined
        ? [{accrual, morn: morn ?? 0, dusk: dusk ?? 0 }]
        : [{accrual, morn: morn ?? last.morn, dusk: dusk ?? last.dusk}, last, ...rest]
    }, [])
    .reverse()
    .slice(1, -1)
    // 这一步操作，将桩的id替换为篱笆的id，将发生额边界替换为发生额，这样就得到了被
    // 分解的分录。
    .map(({accrual, morn, dusk}, i, a) => ({
      accrual: a[i+1] ? toFixed2(a[i+1].accrual - accrual) : 0,
      morn,
      dusk,
    }))
    .slice(0, -1);

  return sortedIndex.map(({accrual, morn, dusk}) => {
    const newMorn = morns[morn].copy();
    const newDusk = dusks[dusk].copy();
    
    newMorn.accrual[mornCol] = accrual;
    newDusk.accrual[duskCol] = accrual;

    return [newMorn, newDusk];
  })
  .concat(paired)
  .map(([morn, dusk]) => {
    assignDest(morn, dusk);
    return [morn, dusk]
  })
}

const decompose = (origEntries) => {
  const copy = origEntries.map(entry => entry.copy());
  const decomp = new List();

  for (let i = 0; i < copy.length; i++) {
    copy[i].accrual.swap();
  }

  let acc = new Accrual();
  for (let start = 0, end = 0; end < copy.length; end++){

    acc.add(copy[end].accrual);

    if (Math.abs(acc.diff()) < 1e-4){
      let groupEntries = copy.slice(start, end+1);
      const drRecs = groupEntries.filter(({accrual}) => accrual.dir() === 'dr');
      const crRecs = groupEntries.filter(({accrual}) => accrual.dir() === 'cr');

      const records = alternate({list: drRecs, col: 'dr'}, {list: crRecs, col: 'cr'})
        .flat()
        .filter(({accrual: {dr, cr}}) => !(dr === 0 && cr === 0));
      decomp.push(...records);

      start = end + 1;
    }
  }

  for (let i = 0; i < decomp.length; i++) {
    decomp[i].accrual.unswap();
    // decomp[i].succ = origEntries;
  }

  return decomp;
}

export default decompose;